import 'package:backend.core/core/domain/entities/api/app_store/api_app_store_purchased_item.dart';
import 'package:backend.core/core/domain/entities/api/google_play/api_google_play_purchased_item.dart';
import 'package:backend.core/core/domain/entities/api/web/api_web_purchased_item.dart';
import 'package:backend.core/core/domain/entities/app_store/app_store_purchased_subscription_info.dart';
import 'package:backend.core/core/domain/entities/app_store/app_store_transaction.dart';
import 'package:backend.core/core/domain/entities/app_store_user_to_user_link.dart';
import 'package:backend.core/core/domain/entities/devices_to_app_store_user_link.dart';
import 'package:backend.core/core/domain/entities/devices_to_google_play_user_link.dart';
import 'package:backend.core/core/domain/entities/google_play/google_play_one_time_purchased_item_info.dart';
import 'package:backend.core/core/domain/entities/google_play/google_play_one_time_purchased_item_transaction.dart';
import 'package:backend.core/core/domain/entities/google_play/google_play_purchase_item_type.dart';
import 'package:backend.core/core/domain/entities/google_play/google_play_purchased_item_info.dart';
import 'package:backend.core/core/domain/entities/google_play/google_play_purchased_subscription_info.dart';
import 'package:backend.core/core/domain/entities/google_play/google_play_purchased_subscription_transaction.dart';
import 'package:backend.core/core/domain/entities/google_play/google_play_transaction.dart';
import 'package:backend.core/core/domain/entities/google_play_user_to_user_link.dart';
import 'package:backend.core/core/domain/entities/web/web_purchase_item.dart';
import 'package:backend.core/core/domain/entities/web/web_purchased_subscription_info.dart';
import 'package:backend.core/core/domain/entities/web/web_transaction.dart';
import 'package:backend.core/core/domain/entities/web/yookassa/yookassa_purchased_subscription_info.dart';
import 'package:backend.core/core/domain/entities/web/yookassa/yookassa_transaction.dart';
import 'package:backend.core/core/domain/errors/failures.dart';
import 'package:backend.core/core/domain/repositories/app_store_purchased_subscription_info_repository/repository.dart';
import 'package:backend.core/core/domain/repositories/app_store_transaction_repository/repository.dart';
import 'package:backend.core/core/domain/repositories/app_store_user_to_user_link_repository/repository.dart';
import 'package:backend.core/core/domain/repositories/devices_to_app_store_user_link_repository/repository.dart';
import 'package:backend.core/core/domain/repositories/devices_to_google_play_user_link_repository/repository.dart';
import 'package:backend.core/core/domain/repositories/google_play_purchased_item_info_repository/repository.dart';
import 'package:backend.core/core/domain/repositories/google_play_transaction_repository/repository.dart';
import 'package:backend.core/core/domain/repositories/google_play_user_to_user_link_repository/repository.dart';
import 'package:backend.core/core/domain/repositories/web_purchase_item_repository/repository.dart';
import 'package:backend.core/core/domain/repositories/web_purchased_subscription_info_repository/repository.dart';
import 'package:backend.core/core/domain/repositories/web_transaction_repository/repository.dart';
import 'package:backend.core/core/domain/utils/either_extension.dart';
import 'package:backend.core/core/domain/utils/uuid_generator.dart';
import 'package:backend.core/logger/logger.dart';
import 'package:backend.purchases/domain/entities/app_store_api/app_store_api_purchased_subscription_renewal_info.dart';
import 'package:backend.purchases/domain/entities/app_store_api/app_store_api_transaction.dart';
import 'package:backend.purchases/domain/entities/google_play_api/google_play_api_purchased_item.dart';
import 'package:backend.purchases/domain/entities/google_play_api/google_play_api_purchased_item_identifier.dart';
import 'package:backend.purchases/domain/entities/google_play_api/google_play_api_revoked_purchased_item.dart';
import 'package:backend.purchases/domain/helpers/api/app_store/create_api_app_store_purchased_items_from_internal_ones.dart';
import 'package:backend.purchases/domain/helpers/api/google_play/create_api_google_play_purchased_items_from_internal_ones.dart';
import 'package:backend.purchases/domain/helpers/api/web/create_api_web_purchased_items_from_internal_ones.dart';
import 'package:backend.purchases/domain/helpers/app_store/create_app_store_purchased_subscription_info_from_api_one.dart';
import 'package:backend.purchases/domain/helpers/app_store/create_app_store_transaction_from_api_one.dart';
import 'package:backend.purchases/domain/helpers/google_play/create_google_play_one_time_purchased_item_info_from_api_one.dart';
import 'package:backend.purchases/domain/helpers/google_play/create_google_play_one_time_purchased_item_transaction_from_api_one.dart';
import 'package:backend.purchases/domain/helpers/google_play/create_google_play_purchased_subscription_info_from_api_one.dart';
import 'package:backend.purchases/domain/helpers/google_play/create_google_play_purchased_subscription_transaction_from_api_one.dart';
import 'package:backend.purchases/domain/helpers/google_play/get_google_play_purchase_item_type.dart';
import 'package:backend.purchases/domain/helpers/google_play/update_google_play_one_time_purchased_item_info_from_api_one.dart';
import 'package:backend.purchases/domain/helpers/google_play/update_google_play_one_time_purchased_item_transaction_from_revoked_purchased_item.dart';
import 'package:backend.purchases/domain/helpers/google_play/update_google_play_purchased_subscription_info_from_api_one.dart';
import 'package:backend.purchases/domain/helpers/google_play/update_google_play_purchased_subscription_transaction_from_api_one.dart';
import 'package:backend.purchases/domain/helpers/google_play/update_google_play_purchased_subscription_transaction_from_revoked_purchased_item.dart';
import 'package:backend.purchases/domain/repositories/app_store_api_purchased_item_repository/repository.dart';
import 'package:backend.purchases/domain/repositories/google_play_api_purchased_item_repository/repository.dart';
import 'package:built_collection/built_collection.dart';
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';

part 'failures.dart';

part 'result.dart';

abstract class ObtainPurchasedItems {
  Future<Either<PurchasedItemObtainmentFailure, PurchasedItemObtainmentResult>> call({
    required String? userId,
    required String? deviceId,
    required BuiltList<GooglePlayApiPurchasedItemIdentifier>? googlePlayApiPurchasedItemIdentifiers,
    required String? appStoreReceipt,
  });
}

class ObtainPurchasedItemsImpl implements ObtainPurchasedItems {
  const ObtainPurchasedItemsImpl({
    required AppStoreUserToUserLinkRepository appStoreUserToUserLinkRepository,
    required GooglePlayUserToUserLinkRepository googlePlayUserToUserLinkRepository,
    required DevicesToAppStoreUserLinkRepository devicesToAppStoreUserLinkRepository,
    required DevicesToGooglePlayUserLinkRepository devicesToGooglePlayUserLinkRepository,
    required AppStoreTransactionRepository appStoreTransactionRepository,
    required AppStorePurchasedSubscriptionInfoRepository
        appStorePurchasedSubscriptionInfoRepository,
    required GooglePlayPurchasedItemInfoRepository googlePlayPurchasedItemInfoRepository,
    required GooglePlayTransactionRepository googlePlayTransactionRepository,
    required AppStoreApiPurchasedItemRepository appStoreApiPurchasedItemRepository,
    required GooglePlayApiPurchasedItemRepository googlePlayApiPurchasedItemRepository,
    required WebPurchaseItemRepository webPurchaseItemRepository,
    required WebTransactionRepository webTransactionRepository,
    required WebPurchasedSubscriptionInfoRepository webPurchasedSubscriptionInfoRepository,
    required GetGooglePlayPurchaseItemType getGooglePlayPurchaseItemType,
    required UpdateGooglePlayOneTimePurchasedItemInfoFromApiOne
        updateGooglePlayOneTimePurchasedItemInfoFromApiOne,
    required UpdateGooglePlayPurchasedSubscriptionInfoFromApiOne
        updateGooglePlayPurchasedSubscriptionInfoFromApiOne,
    required CreateGooglePlayOneTimePurchasedItemInfoFromApiOne
        createGooglePlayOneTimePurchasedItemInfoFromApiOne,
    required CreateGooglePlayPurchasedSubscriptionInfoFromApiOne
        createGooglePlayPurchasedSubscriptionInfoFromApiOne,
    required UpdateGooglePlayPurchasedSubscriptionTransactionFromApiOne
        updateGooglePlayPurchasedSubscriptionTransactionFromApiOne,
    required CreateGooglePlayOneTimePurchasedItemTransactionFromApiOne
        createGooglePlayOneTimePurchasedItemTransactionFromApiOne,
    required CreateGooglePlayPurchasedSubscriptionTransactionFromApiOne
        createGooglePlayPurchasedSubscriptionTransactionFromApiOne,
    required UpdateGooglePlayPurchasedSubscriptionTransactionFromRevokedPurchasedItem
        updateGooglePlayPurchasedSubscriptionTransactionFromRevokedPurchasedItem,
    required UpdateGooglePlayOneTimePurchasedItemTransactionFromRevokedPurchasedItem
        updateGooglePlayOneTimePurchasedItemTransactionFromRevokedPurchasedItem,
    required CreateAppStoreTransactionFromApiOne createAppStoreTransactionFromApiOne,
    required CreateAppStorePurchasedSubscriptionInfoFromApiOne
        createAppStorePurchasedSubscriptionInfoFromApiOne,
    required CreateApiAppStorePurchasedItemsFromInternalOnes
        createApiAppStorePurchasedItemsFromInternalOnes,
    required CreateApiGooglePlayPurchasedItemsFromInternalOnes
        createApiGooglePlayPurchasedItemsFromInternalOnes,
    required CreateApiWebPurchasedItemsFromInternalOnes createApiWebPurchasedItemsFromInternalOnes,
  })  : _appStoreUserToUserLinkRepository = appStoreUserToUserLinkRepository,
        _googlePlayUserToUserLinkRepository = googlePlayUserToUserLinkRepository,
        _devicesToAppStoreUserLinkRepository = devicesToAppStoreUserLinkRepository,
        _devicesToGooglePlayUserLinkRepository = devicesToGooglePlayUserLinkRepository,
        _appStoreTransactionRepository = appStoreTransactionRepository,
        _appStorePurchasedSubscriptionInfoRepository = appStorePurchasedSubscriptionInfoRepository,
        _googlePlayPurchasedItemInfoRepository = googlePlayPurchasedItemInfoRepository,
        _googlePlayTransactionRepository = googlePlayTransactionRepository,
        _appStoreApiPurchasedItemRepository = appStoreApiPurchasedItemRepository,
        _googlePlayApiPurchasedItemRepository = googlePlayApiPurchasedItemRepository,
        _webPurchaseItemRepository = webPurchaseItemRepository,
        _webTransactionRepository = webTransactionRepository,
        _webPurchasedSubscriptionInfoRepository = webPurchasedSubscriptionInfoRepository,
        _getGooglePlayPurchaseItemType = getGooglePlayPurchaseItemType,
        _updateGooglePlayOneTimePurchasedItemInfoFromApiOne =
            updateGooglePlayOneTimePurchasedItemInfoFromApiOne,
        _updateGooglePlayPurchasedSubscriptionInfoFromApiOne =
            updateGooglePlayPurchasedSubscriptionInfoFromApiOne,
        _createGooglePlayOneTimePurchasedItemInfoFromApiOne =
            createGooglePlayOneTimePurchasedItemInfoFromApiOne,
        _createGooglePlayPurchasedSubscriptionInfoFromApiOne =
            createGooglePlayPurchasedSubscriptionInfoFromApiOne,
        _updateGooglePlayPurchasedSubscriptionTransactionFromApiOne =
            updateGooglePlayPurchasedSubscriptionTransactionFromApiOne,
        _createGooglePlayOneTimePurchasedItemTransactionFromApiOne =
            createGooglePlayOneTimePurchasedItemTransactionFromApiOne,
        _createGooglePlayPurchasedSubscriptionTransactionFromApiOne =
            createGooglePlayPurchasedSubscriptionTransactionFromApiOne,
        _updateGooglePlayPurchasedSubscriptionTransactionFromRevokedPurchasedItem =
            updateGooglePlayPurchasedSubscriptionTransactionFromRevokedPurchasedItem,
        _updateGooglePlayOneTimePurchasedItemTransactionFromRevokedPurchasedItem =
            updateGooglePlayOneTimePurchasedItemTransactionFromRevokedPurchasedItem,
        _createAppStoreTransactionFromApiOne = createAppStoreTransactionFromApiOne,
        _createAppStorePurchasedSubscriptionInfoFromApiOne =
            createAppStorePurchasedSubscriptionInfoFromApiOne,
        _createApiAppStorePurchasedItemsFromInternalOnes =
            createApiAppStorePurchasedItemsFromInternalOnes,
        _createApiGooglePlayPurchasedItemsFromInternalOnes =
            createApiGooglePlayPurchasedItemsFromInternalOnes,
        _createApiWebPurchasedItemsFromInternalOnes = createApiWebPurchasedItemsFromInternalOnes;

  final AppStoreUserToUserLinkRepository _appStoreUserToUserLinkRepository;
  final GooglePlayUserToUserLinkRepository _googlePlayUserToUserLinkRepository;
  final DevicesToAppStoreUserLinkRepository _devicesToAppStoreUserLinkRepository;
  final DevicesToGooglePlayUserLinkRepository _devicesToGooglePlayUserLinkRepository;
  final AppStoreTransactionRepository _appStoreTransactionRepository;
  final AppStorePurchasedSubscriptionInfoRepository _appStorePurchasedSubscriptionInfoRepository;
  final GooglePlayPurchasedItemInfoRepository _googlePlayPurchasedItemInfoRepository;
  final GooglePlayTransactionRepository _googlePlayTransactionRepository;
  final AppStoreApiPurchasedItemRepository _appStoreApiPurchasedItemRepository;
  final GooglePlayApiPurchasedItemRepository _googlePlayApiPurchasedItemRepository;
  final WebPurchaseItemRepository _webPurchaseItemRepository;
  final WebTransactionRepository _webTransactionRepository;
  final WebPurchasedSubscriptionInfoRepository _webPurchasedSubscriptionInfoRepository;

  final GetGooglePlayPurchaseItemType _getGooglePlayPurchaseItemType;
  final UpdateGooglePlayOneTimePurchasedItemInfoFromApiOne
      _updateGooglePlayOneTimePurchasedItemInfoFromApiOne;
  final UpdateGooglePlayPurchasedSubscriptionInfoFromApiOne
      _updateGooglePlayPurchasedSubscriptionInfoFromApiOne;
  final CreateGooglePlayOneTimePurchasedItemInfoFromApiOne
      _createGooglePlayOneTimePurchasedItemInfoFromApiOne;
  final CreateGooglePlayPurchasedSubscriptionInfoFromApiOne
      _createGooglePlayPurchasedSubscriptionInfoFromApiOne;
  final UpdateGooglePlayPurchasedSubscriptionTransactionFromApiOne
      _updateGooglePlayPurchasedSubscriptionTransactionFromApiOne;
  final CreateGooglePlayOneTimePurchasedItemTransactionFromApiOne
      _createGooglePlayOneTimePurchasedItemTransactionFromApiOne;
  final CreateGooglePlayPurchasedSubscriptionTransactionFromApiOne
      _createGooglePlayPurchasedSubscriptionTransactionFromApiOne;
  final UpdateGooglePlayPurchasedSubscriptionTransactionFromRevokedPurchasedItem
      _updateGooglePlayPurchasedSubscriptionTransactionFromRevokedPurchasedItem;
  final UpdateGooglePlayOneTimePurchasedItemTransactionFromRevokedPurchasedItem
      _updateGooglePlayOneTimePurchasedItemTransactionFromRevokedPurchasedItem;
  final CreateAppStoreTransactionFromApiOne _createAppStoreTransactionFromApiOne;
  final CreateAppStorePurchasedSubscriptionInfoFromApiOne
      _createAppStorePurchasedSubscriptionInfoFromApiOne;
  final CreateApiAppStorePurchasedItemsFromInternalOnes
      _createApiAppStorePurchasedItemsFromInternalOnes;
  final CreateApiGooglePlayPurchasedItemsFromInternalOnes
      _createApiGooglePlayPurchasedItemsFromInternalOnes;
  final CreateApiWebPurchasedItemsFromInternalOnes _createApiWebPurchasedItemsFromInternalOnes;

  @override
  Future<Either<PurchasedItemObtainmentFailure, PurchasedItemObtainmentResult>> call({
    required String? userId,
    required String? deviceId,
    required BuiltList<GooglePlayApiPurchasedItemIdentifier>? googlePlayApiPurchasedItemIdentifiers,
    required String? appStoreReceipt,
  }) async {
    String? linkedAppStoreUserId;
    String? linkedGooglePlayUserId;

    if (userId != null) {
      final futureResults = await Future.wait([
        _appStoreUserToUserLinkRepository.findLinkByUserId(
          userId: userId,
        ),
        _googlePlayUserToUserLinkRepository.findLinkByUserId(
          userId: userId,
        ),
      ]);

      var i = 0;
      final appStoreUserLink = futureResults[i++] as AppStoreUserToUserLink?;
      final googlePlayUserLink = futureResults[i++] as GooglePlayUserToUserLink?;

      linkedAppStoreUserId = appStoreUserLink?.appStoreUserId;
      linkedGooglePlayUserId = googlePlayUserLink?.googlePlayUserId;
    }

    // find updatable google play purchased item identifiers

    String? effectiveGooglePlayUserId;

    final toBeRestoredGooglePlayApiPurchasedItemIdentifiers =
        <GooglePlayApiPurchasedItemIdentifier>{};

    var apiGooglePlayPurchasedItems = BuiltList<ApiGooglePlayPurchasedItem>();

    if (googlePlayApiPurchasedItemIdentifiers != null) {
      final purchaseTokens = googlePlayApiPurchasedItemIdentifiers.map((purchasedItemIdentifier) {
        return purchasedItemIdentifier.purchaseToken;
      }).toBuiltList();

      final nullablePurchasedItemInfos =
          await _googlePlayPurchasedItemInfoRepository.findPurchasedItemInfosByTokens(
        purchaseTokens: purchaseTokens,
      );

      final purchasedItemInfos =
          nullablePurchasedItemInfos.whereType<GooglePlayPurchasedItemInfo>();

      if (purchasedItemInfos.isNotEmpty) {
        effectiveGooglePlayUserId = purchasedItemInfos.first.googlePlayUserId;
      }

      for (var i = 0; i < nullablePurchasedItemInfos.length; i++) {
        if (nullablePurchasedItemInfos[i] == null) {
          toBeRestoredGooglePlayApiPurchasedItemIdentifiers
              .add(googlePlayApiPurchasedItemIdentifiers[i]);
        }
      }
    } else if (linkedGooglePlayUserId != null) {
      effectiveGooglePlayUserId = linkedGooglePlayUserId;
    }

    // update google play purchased items

    if (toBeRestoredGooglePlayApiPurchasedItemIdentifiers.isNotEmpty) {
      effectiveGooglePlayUserId ??= UuidGenerator().generateUuid();

      final failureOrRevokedPurchasedItems =
          await _googlePlayApiPurchasedItemRepository.getRevokedPurchasedItems();

      final revokedPurchasedItemMap = <String, GooglePlayApiRevokedPurchasedItem>{};

      if (failureOrRevokedPurchasedItems.isRight()) {
        final revokedPurchasedItems = failureOrRevokedPurchasedItems.asRight();

        for (final revokedPurchasedItem in revokedPurchasedItems) {
          revokedPurchasedItemMap[revokedPurchasedItem.orderId] = revokedPurchasedItem;
        }
      }

      final updatedApiPurchasedItemMap = <String, GooglePlayApiPurchasedItem>{};

      for (final apiPurchasedItemIdentifier in toBeRestoredGooglePlayApiPurchasedItemIdentifiers) {
        final failureOrPurchaseItemType = _getGooglePlayPurchaseItemType(
          purchaseItemSku: apiPurchasedItemIdentifier.purchaseItemSku,
        );

        if (failureOrPurchaseItemType.isLeft()) {
          Logger().e(
            'Unknown purchase item sku: ${apiPurchasedItemIdentifier.purchaseItemSku}',
            null,
            StackTrace.current,
          );

          continue;
        }

        final purchaseItemType = failureOrPurchaseItemType.asRight();

        if (purchaseItemType.isSubscription) {
          final failureOrApiPurchasedSubscription =
              await _googlePlayApiPurchasedItemRepository.getPurchasedSubscription(
            purchasedItemIdentifier: apiPurchasedItemIdentifier,
          );

          if (failureOrApiPurchasedSubscription.isRight()) {
            final updatedApiPurchasedSubscription = failureOrApiPurchasedSubscription.asRight();

            updatedApiPurchasedItemMap[apiPurchasedItemIdentifier.purchaseToken] =
                updatedApiPurchasedSubscription;
          }
        } else if (purchaseItemType.isOneTimePurchaseItem) {
          final failureOrApiOneTimePurchasedItem =
              await _googlePlayApiPurchasedItemRepository.getOneTimePurchasedItem(
            purchasedItemIdentifier: apiPurchasedItemIdentifier,
          );

          if (failureOrApiOneTimePurchasedItem.isRight()) {
            final updatedApiOneTimePurchasedItem = failureOrApiOneTimePurchasedItem.asRight();

            updatedApiPurchasedItemMap[apiPurchasedItemIdentifier.purchaseToken] =
                updatedApiOneTimePurchasedItem;
          }
        }
      }

      final futureResults = await Future.wait([
        _googlePlayPurchasedItemInfoRepository.findUserPurchasedItemInfos(
          googlePlayUserId: effectiveGooglePlayUserId,
        ),
        _googlePlayTransactionRepository.findUserTransactions(
          googlePlayUserId: effectiveGooglePlayUserId,
        ),
      ]);

      var i = 0;
      final savedUserPurchasedItemInfos =
          futureResults[i++] as BuiltList<GooglePlayPurchasedItemInfo>;
      final savedUserPurchasedItemTransactions =
          futureResults[i++] as BuiltList<GooglePlayTransaction>;

      final savedUserPurchasedItemInfoMap = <String, GooglePlayPurchasedItemInfo>{};

      for (final purchasedItemInfo in savedUserPurchasedItemInfos) {
        savedUserPurchasedItemInfoMap[purchasedItemInfo.purchaseToken] = purchasedItemInfo;
      }

      final savedUserPurchasedItemTransactionMap = <String, GooglePlayTransaction>{};

      for (final purchasedItemTransaction in savedUserPurchasedItemTransactions) {
        savedUserPurchasedItemTransactionMap[purchasedItemTransaction.orderId] =
            purchasedItemTransaction;
      }

      final updatedPurchasedItemInfos = <GooglePlayPurchasedItemInfo>[];
      final updatedPurchasedItemTransactions = <GooglePlayTransaction>[];

      for (final updatedApiPurchasedItemMapEntry in updatedApiPurchasedItemMap.entries) {
        final purchaseToken = updatedApiPurchasedItemMapEntry.key;
        final updatedApiPurchasedItem = updatedApiPurchasedItemMapEntry.value;
        final orderId = updatedApiPurchasedItem.orderId;

        final purchaseItemSku = toBeRestoredGooglePlayApiPurchasedItemIdentifiers
            .firstWhere((apiPurchasedItemIdentifier) {
          return apiPurchasedItemIdentifier.purchaseToken == purchaseToken;
        }).purchaseItemSku;

        if (savedUserPurchasedItemInfoMap.containsKey(purchaseToken)) {
          final purchasedItemInfo = savedUserPurchasedItemInfoMap[purchaseToken]!;

          if (purchasedItemInfo is GooglePlayOneTimePurchasedItemInfo) {
            final oneTimePurchasedItemInfo = purchasedItemInfo;
            final updatedApiOneTimePurchasedItem =
                updatedApiPurchasedItem as GooglePlayApiOneTimePurchasedItem;

            final updatedOneTimePurchasedItemInfo =
                _updateGooglePlayOneTimePurchasedItemInfoFromApiOne(
              oneTimePurchasedItemInfo: oneTimePurchasedItemInfo,
              updatedApiOneTimePurchasedItem: updatedApiOneTimePurchasedItem,
            );

            updatedPurchasedItemInfos.add(updatedOneTimePurchasedItemInfo);
          } else if (purchasedItemInfo is GooglePlayPurchasedSubscriptionInfo) {
            final purchasedSubscriptionInfo = purchasedItemInfo;
            final updatedApiPurchasedSubscription =
                updatedApiPurchasedItem as GooglePlayApiPurchasedSubscription;

            final updatedPurchasedSubscriptionInfo =
                _updateGooglePlayPurchasedSubscriptionInfoFromApiOne(
              purchasedSubscriptionInfo: purchasedSubscriptionInfo,
              updatedApiPurchasedSubscription: updatedApiPurchasedSubscription,
            );

            updatedPurchasedItemInfos.add(updatedPurchasedSubscriptionInfo);
          }
        } else {
          if (updatedApiPurchasedItem is GooglePlayApiOneTimePurchasedItem) {
            final apiOneTimePurchasedItem = updatedApiPurchasedItem;

            final oneTimePurchasedItemInfo = _createGooglePlayOneTimePurchasedItemInfoFromApiOne(
              apiOneTimePurchasedItem: apiOneTimePurchasedItem,
              googlePlayUserId: effectiveGooglePlayUserId,
              purchaseToken: purchaseToken,
              purchaseItemSku: purchaseItemSku,
            );

            updatedPurchasedItemInfos.add(oneTimePurchasedItemInfo);
          } else if (updatedApiPurchasedItem is GooglePlayApiPurchasedSubscription) {
            final apiPurchasedSubscription = updatedApiPurchasedItem;

            final purchasedSubscriptionInfo = _createGooglePlayPurchasedSubscriptionInfoFromApiOne(
              apiPurchasedSubscription: apiPurchasedSubscription,
              googlePlayUserId: effectiveGooglePlayUserId,
              purchaseToken: purchaseToken,
              purchaseItemSku: purchaseItemSku,
            );

            updatedPurchasedItemInfos.add(purchasedSubscriptionInfo);
          }
        }

        if (savedUserPurchasedItemTransactionMap.containsKey(orderId)) {
          final purchasedItemTransaction = savedUserPurchasedItemTransactionMap[orderId]!;

          if (purchasedItemTransaction is GooglePlayOneTimePurchasedItemTransaction) {
            final oneTimePurchasedItemTransaction = purchasedItemTransaction;

            // add for revocation check later
            updatedPurchasedItemTransactions.add(oneTimePurchasedItemTransaction);
          } else if (purchasedItemTransaction is GooglePlayPurchasedSubscriptionTransaction) {
            final purchasedSubscriptionTransaction = purchasedItemTransaction;
            final updatedApiPurchasedSubscription =
                updatedApiPurchasedItem as GooglePlayApiPurchasedSubscription;

            final updatedPurchasedSubscriptionTransaction =
                _updateGooglePlayPurchasedSubscriptionTransactionFromApiOne(
              purchasedSubscriptionTransaction: purchasedSubscriptionTransaction,
              updatedApiPurchasedSubscription: updatedApiPurchasedSubscription,
            );

            updatedPurchasedItemTransactions.add(updatedPurchasedSubscriptionTransaction);
          }
        } else {
          if (updatedApiPurchasedItem is GooglePlayApiOneTimePurchasedItem) {
            final apiOneTimePurchasedItem = updatedApiPurchasedItem;

            final oneTimePurchasedItemTransaction =
                _createGooglePlayOneTimePurchasedItemTransactionFromApiOne(
              apiOneTimePurchasedItem: apiOneTimePurchasedItem,
              googlePlayUserId: effectiveGooglePlayUserId,
              purchaseToken: purchaseToken,
              purchaseItemSku: purchaseItemSku,
              revocationInfo: null,
            );

            updatedPurchasedItemTransactions.add(oneTimePurchasedItemTransaction);
          } else if (updatedApiPurchasedItem is GooglePlayApiPurchasedSubscription) {
            final apiPurchasedSubscription = updatedApiPurchasedItem;

            final purchasedSubscriptionTransaction =
                _createGooglePlayPurchasedSubscriptionTransactionFromApiOne(
              apiPurchasedSubscription: apiPurchasedSubscription,
              googlePlayUserId: effectiveGooglePlayUserId,
              purchaseToken: purchaseToken,
              purchaseItemSku: purchaseItemSku,
              revocationInfo: null,
            );

            updatedPurchasedItemTransactions.add(purchasedSubscriptionTransaction);
          }
        }
      }

      for (var i = 0; i < updatedPurchasedItemTransactions.length; i++) {
        final updatedPurchasedItemTransaction = updatedPurchasedItemTransactions[i];

        final revokedPurchasedItem =
            revokedPurchasedItemMap[updatedPurchasedItemTransaction.orderId];

        if (revokedPurchasedItem != null) {
          if (updatedPurchasedItemTransaction is GooglePlayOneTimePurchasedItemTransaction) {
            final updatedOneTimePurchasedItemTransaction = updatedPurchasedItemTransaction;

            updatedPurchasedItemTransactions[i] =
                _updateGooglePlayOneTimePurchasedItemTransactionFromRevokedPurchasedItem(
              oneTimePurchasedItemTransaction: updatedOneTimePurchasedItemTransaction,
              revokedPurchasedItem: revokedPurchasedItem,
            );
          } else if (updatedPurchasedItemTransaction
              is GooglePlayPurchasedSubscriptionTransaction) {
            final updatedPurchasedSubscriptionTransaction = updatedPurchasedItemTransaction;

            updatedPurchasedItemTransactions[i] =
                _updateGooglePlayPurchasedSubscriptionTransactionFromRevokedPurchasedItem(
              purchasedSubscriptionTransaction: updatedPurchasedSubscriptionTransaction,
              revokedPurchasedItem: revokedPurchasedItem,
            );
          }
        }
      }

      await Future.wait([
        _googlePlayPurchasedItemInfoRepository.upsertPurchasedItemInfos(
          purchasedItemInfos: updatedPurchasedItemInfos.toBuiltList(),
        ),
        _googlePlayTransactionRepository.upsertTransactions(
          purchasedItemTransactions: updatedPurchasedItemTransactions.toBuiltList(),
        ),
      ]);
    }

    if (effectiveGooglePlayUserId != null) {
      final futureResults = await Future.wait([
        _googlePlayPurchasedItemInfoRepository.findUserPurchasedItemInfos(
          googlePlayUserId: effectiveGooglePlayUserId,
        ),
        _googlePlayTransactionRepository.findUserTransactions(
          googlePlayUserId: effectiveGooglePlayUserId,
        ),
      ]);

      var i = 0;
      final purchasedItemInfos = futureResults[i++] as BuiltList<GooglePlayPurchasedItemInfo>;
      final purchasedItemTransactions = futureResults[i++] as BuiltList<GooglePlayTransaction>;

      apiGooglePlayPurchasedItems = _createApiGooglePlayPurchasedItemsFromInternalOnes(
        purchasedItemInfos: purchasedItemInfos,
        purchasedItemTransactions: purchasedItemTransactions,
      );
    }

    // change google play user to user link if needed

    if (userId != null &&
        effectiveGooglePlayUserId != null &&
        effectiveGooglePlayUserId != linkedGooglePlayUserId) {
      final link = GooglePlayUserToUserLink(
        userId: userId,
        googlePlayUserId: effectiveGooglePlayUserId,
      );

      await _googlePlayUserToUserLinkRepository.upsertLink(
        link: link,
      );
    }

    // change devices to google play user link if needed

    if (effectiveGooglePlayUserId != null && deviceId != null) {
      final link = await _devicesToGooglePlayUserLinkRepository.findLinkByGooglePlayUserId(
        googlePlayUserId: effectiveGooglePlayUserId,
      );

      DevicesToGooglePlayUserLink? updatedLink;

      if (link == null) {
        updatedLink = DevicesToGooglePlayUserLink(
          googlePlayUserId: effectiveGooglePlayUserId,
          deviceIds: BuiltList([
            deviceId,
          ]),
        );
      } else {
        if (!link.deviceIds.contains(deviceId)) {
          final updatedDeviceIds = link.deviceIds.rebuild((deviceIds) {
            deviceIds.add(deviceId);
          });

          updatedLink = link.copyWith(
            deviceIds: () => updatedDeviceIds,
          );
        }
      }

      if (updatedLink != null) {
        await _devicesToGooglePlayUserLinkRepository.upsertLink(
          link: updatedLink,
        );
      }
    }

    // obtain app store purchased items

    String? effectiveAppStoreUserId;

    var apiAppStorePurchasedItems = BuiltList<ApiAppStorePurchasedItem>();

    if (appStoreReceipt != null) {
      final failureOrOriginalTransactionId =
          await _appStoreApiPurchasedItemRepository.getOriginalTransactionId(
        receipt: appStoreReceipt,
      );

      if (failureOrOriginalTransactionId.isLeft()) {
        return left(const PurchasedItemObtainmentFailure());
      }

      final originalTransactionId = failureOrOriginalTransactionId.asRight();

      var transactions =
          await _appStoreTransactionRepository.findTransactionsByOriginalTransactionId(
        originalTransactionId: originalTransactionId,
      );

      if (transactions.isEmpty) {
        final futureResults = await Future.wait([
          _appStoreApiPurchasedItemRepository.getTransactionHistory(
            originalTransactionId: originalTransactionId,
          ),
          _appStoreApiPurchasedItemRepository.getRenewalInfos(
            originalTransactionId: originalTransactionId,
          ),
        ]);

        var i = 0;
        final failureOrTransactionHistory = futureResults[i++] as Either<
            AppStoreApiTransactionHistoryGettingFailure, BuiltList<AppStoreApiTransaction>>;
        final failureOrRenewalInfos = futureResults[i++] as Either<
            AppStoreApiPurchasedSubscriptionRenewalInfoGettingFailure,
            BuiltList<AppStoreApiPurchasedSubscriptionRenewalInfo>>;

        if (failureOrTransactionHistory.isRight() && failureOrRenewalInfos.isRight()) {
          final apiTransactionHistory = failureOrTransactionHistory.asRight();

          final apiTransactionIds = apiTransactionHistory.map((transaction) {
            return transaction.id;
          }).toBuiltList();

          final nullableTransactions = await _appStoreTransactionRepository.findTransactionsByIds(
            ids: apiTransactionIds,
          );

          transactions = nullableTransactions.whereType<AppStoreTransaction>().toBuiltList();

          if (transactions.isEmpty) {
            effectiveAppStoreUserId = UuidGenerator().generateUuid();
          } else {
            effectiveAppStoreUserId = transactions.first.appStoreUserId;
          }

          final updatedTransactions = apiTransactionHistory.map((apiTransaction) {
            return _createAppStoreTransactionFromApiOne(
              apiTransaction: apiTransaction,
              appStoreUserId: effectiveAppStoreUserId!,
            );
          }).toBuiltList();

          final apiRenewalInfos = failureOrRenewalInfos.asRight();

          final updatedPurchasedSubscriptionInfos = apiRenewalInfos.map((apiRenewalInfo) {
            return _createAppStorePurchasedSubscriptionInfoFromApiOne(
              apiRenewalInfo: apiRenewalInfo,
              appStoreUserId: effectiveAppStoreUserId!,
            );
          }).toBuiltList();

          await Future.wait([
            _appStoreTransactionRepository.replaceUserTransactions(
              appStoreUserId: effectiveAppStoreUserId,
              transactions: updatedTransactions,
            ),
            _appStorePurchasedSubscriptionInfoRepository.replaceUserPurchasedSubscriptionInfos(
              appStoreUserId: effectiveAppStoreUserId,
              purchasedSubscriptionInfos: updatedPurchasedSubscriptionInfos,
            ),
          ]);

          apiAppStorePurchasedItems = _createApiAppStorePurchasedItemsFromInternalOnes(
            transactions: updatedTransactions,
            purchasedSubscriptionInfos: updatedPurchasedSubscriptionInfos,
          );
        }
      } else {
        effectiveAppStoreUserId = transactions.first.appStoreUserId;

        final futureResults = await Future.wait([
          _appStoreTransactionRepository.findUserTransactions(
            appStoreUserId: effectiveAppStoreUserId,
          ),
          _appStorePurchasedSubscriptionInfoRepository.findUserPurchasedSubscriptionInfos(
            appStoreUserId: effectiveAppStoreUserId,
          ),
        ]);

        var i = 0;

        transactions = futureResults[i++] as BuiltList<AppStoreTransaction>;
        final purchasedSubscriptionInfos =
            futureResults[i++] as BuiltList<AppStorePurchasedSubscriptionInfo>;

        apiAppStorePurchasedItems = _createApiAppStorePurchasedItemsFromInternalOnes(
          transactions: transactions,
          purchasedSubscriptionInfos: purchasedSubscriptionInfos,
        );
      }
    } else if (linkedAppStoreUserId != null) {
      effectiveAppStoreUserId = linkedAppStoreUserId;

      final futureResults = await Future.wait([
        _appStoreTransactionRepository.findUserTransactions(
          appStoreUserId: linkedAppStoreUserId,
        ),
        _appStorePurchasedSubscriptionInfoRepository.findUserPurchasedSubscriptionInfos(
          appStoreUserId: linkedAppStoreUserId,
        )
      ]);

      var i = 0;

      final transactions = futureResults[i++] as BuiltList<AppStoreTransaction>;
      final purchasedSubscriptionInfos =
          futureResults[i++] as BuiltList<AppStorePurchasedSubscriptionInfo>;

      apiAppStorePurchasedItems = _createApiAppStorePurchasedItemsFromInternalOnes(
        transactions: transactions,
        purchasedSubscriptionInfos: purchasedSubscriptionInfos,
      );
    }

    // change app store user link if needed

    if (userId != null &&
        effectiveAppStoreUserId != null &&
        effectiveAppStoreUserId != linkedAppStoreUserId) {
      final userLink = AppStoreUserToUserLink(
        userId: userId,
        appStoreUserId: effectiveAppStoreUserId,
      );

      await _appStoreUserToUserLinkRepository.upsertLink(
        link: userLink,
      );
    }

    // change devices to app store user link if needed

    if (effectiveAppStoreUserId != null && deviceId != null && deviceId.isNotEmpty) {
      final link = await _devicesToAppStoreUserLinkRepository.findLinkByAppStoreUserId(
        appStoreUserId: effectiveAppStoreUserId,
      );

      DevicesToAppStoreUserLink? updatedLink;

      if (link == null) {
        updatedLink = DevicesToAppStoreUserLink(
          appStoreUserId: effectiveAppStoreUserId,
          deviceIds: BuiltList([
            deviceId,
          ]),
        );
      } else {
        if (!link.deviceIds.contains(deviceId)) {
          final updatedDeviceIds = link.deviceIds.rebuild((deviceIds) {
            deviceIds.add(deviceId);
          });

          updatedLink = link.copyWith(
            deviceIds: () => updatedDeviceIds,
          );
        }
      }

      if (updatedLink != null) {
        await _devicesToAppStoreUserLinkRepository.upsertLink(
          link: updatedLink,
        );
      }
    }

    // get yookassa purchased items

    var apiWebPurchasedItems = BuiltList<ApiWebPurchasedItem>();

    if (userId != null) {
      final futureResults = await Future.wait([
        _webTransactionRepository.findUserTransactions(
          userId: userId,
        ),
        _webPurchasedSubscriptionInfoRepository.findUserPurchasedSubscriptionInfos(
          userId: userId,
        ),
        _webPurchaseItemRepository.findAllPurchaseItems(),
      ]);

      var i = 0;

      final transactions = futureResults[i++] as BuiltList<WebTransaction>;
      final purchasedSubscriptionInfos =
          futureResults[i++] as BuiltList<WebPurchasedSubscriptionInfo>;
      final purchaseItems = futureResults[i++] as BuiltList<WebPurchaseItem>;

      final yookassaTransactions = transactions.whereType<YookassaTransaction>().toBuiltList();

      final yookassaPurchasedSubscriptionInfos =
          purchasedSubscriptionInfos.whereType<YookassaPurchasedSubscriptionInfo>().toBuiltList();

      final purchaseItemMap = <String, WebPurchaseItem>{};

      for (final purchaseItem in purchaseItems) {
        purchaseItemMap[purchaseItem.sku] = purchaseItem;
      }

      apiWebPurchasedItems = _createApiWebPurchasedItemsFromInternalOnes(
        transactions: yookassaTransactions,
        purchasedSubscriptionInfos: yookassaPurchasedSubscriptionInfos,
        purchaseItemMap: purchaseItemMap.build(),
      );
    }

    final result = PurchasedItemObtainmentResult(
      apiAppStorePurchasedItems: apiAppStorePurchasedItems,
      apiGooglePlayPurchasedItems: apiGooglePlayPurchasedItems,
      apiWebPurchasedItems: apiWebPurchasedItems,
    );

    return right(result);
  }
}
